// -*- coding: utf-8 -*- 
/**
 * Project: Java8InActionT
 * Creator: yanking
 * Create time: 2022-01-15 17:21
 * IDE: IntelliJ IDEA
 * Introduction:
 */
package com.StreamTrain;

import com.measurePerf.accumulator;
import com.measurePerf.measureUtils;
import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.function.IntSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

public class StreamTrain {
    public static <A> List<A> takeWhile(List<A> prime, Predicate<A> p) {
        int i = 0;
        for (A item : prime) {
            if (!p.test(item)) {
                return prime.subList(0, i);
            }
            ++i;
        }
        return prime;
    }

    public static boolean isPrime(List<Integer> prime, int candidate) {
        int candidateRoot = (int) Math.sqrt((double) candidate);
        return takeWhile(prime, i -> i <= candidate).stream().noneMatch(p -> candidate % p == 0);
    }

    public Supplier<Map<Boolean, List<Integer>>> supplier() {
        // init a accumulator
        return () ->
                new HashMap<Boolean, List<Integer>>() {{
                    put(true, new ArrayList<Integer>());
                    put(false, new ArrayList<Integer>());
                }};
    }

    @Test
    public void shout() {
        // 批量实例化Dish对象
        String[] name = {"fish", "pig", "chicken"};
        Float[] fee = {20f, 28f, 50f};
        Dishkind.DishKind[] dashikis = {Dishkind.DishKind.FISH, Dishkind.DishKind.MEAT, Dishkind.DishKind.OTHER};
        Double[] calories = {66.0, 88.8, 99.9};
        List<Dish> dishes = Dish.map(name, fee, dashikis, calories, Dish::new);
        // dishes.stream().filter((a) -> a.getFee() > 20).forEach(System.out::println);
        // Comparator<Dish> caloriesComparator = Comparator.comparingDouble(Dish::getCalories);
/*        System.out.println(dishes.stream().max(Dish.caloriesComparator));
        System.out.println(dishes.stream().collect(summingDouble(Dish::getCalories)));
        System.out.println(dishes.stream().mapToDouble(Dish::getCalories).sum());
        System.out.println(dishes.stream().collect(averagingDouble(Dish::getCalories)));
        System.out.println(dishes.stream().collect(summarizingDouble(Dish::getCalories)));*/
        // System.out.println(dishes.stream().map(Dish::getName).collect(joining(":")));
        // System.out.println(dishes.stream().reduce((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
        // System.out.println(dishes.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)));

    }

    @Test
    public void shout01() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
        stream.reduce(
                new ArrayList<Integer>(),
                (List<Integer> l1, Integer e) -> {
                    l1.add(e);
                    return l1;
                },
                (List<Integer> l1, List<Integer> l2) -> {
                    l1.addAll(l2);
                    return l1;
                }
        ).forEach(System.out::println);
        System.out.println("After reducing method");
        stream.collect(Collectors.toList()).forEach(System.out::println);
    }

    @Test
    public void shout02() {
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8).collect(reducing(0, a -> a, Integer::sum)));
        //  System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8).map(a -> a).reduce(0, Integer::sum));
    }

    @Test
    public void shout03() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8);
        System.out.println(stream.collect(Dish.counting()));
    }

    // {FAT=[Dish{fee=20.5, name='fish', calories=888.888, dishkind=FISH},
    // Dish{fee=10.5, name='cabbage', calories=888.888, dishkind=VEGETABLE},
    // Dish{fee=80.5, name='beef', calories=999.88, dishkind=MEAT}],
    // DIET=[Dish{fee=30.8, name='pork', calories=66.88, dishkind=MEAT},
    // Dish{fee=60.5, name='chicken', calories=99.88, dishkind=MEAT}]}

    @Test
    public void shout04() {
        System.out.println(Stream.empty().mapToDouble(Object::hashCode).sum());  // 0
    }

    @Test
    public void shout05() {
        Dishkind.DishKind meat = Dishkind.DishKind.MEAT;
        Dishkind.DishKind other = Dishkind.DishKind.OTHER;
        Dishkind.DishKind fish = Dishkind.DishKind.FISH;
        Dishkind.DishKind vegetable = Dishkind.DishKind.VEGETABLE;
        List<Dish> dishes = Arrays.asList(
                new Dish("fish", 20.5f, fish, 888.888),
                new Dish("pork", 30.8f, meat, 66.88),
                new Dish("cabbage", 10.5f, vegetable, 888.888),
                new Dish("chicken", 60.5f, meat, 99.88),
                new Dish("beef", 80.5f, meat, 999.88)
        );
        // use the custom groupingBy type
        System.out.println(dishes.stream().collect(groupingBy(
                dish -> {
                    if (dish.getCalories() < 400) {
                        return Dish.CaloriesLevel.DIET;
                    } else if (dish.getCalories() < 700) {
                        return Dish.CaloriesLevel.NORMAL;
                    } else {
                        return Dish.CaloriesLevel.FAT;
                    }
                }
        )));
    }

    @Test
    public void shout06() {
        // achieve the grouping by multiple level
        Dishkind.DishKind meat = Dishkind.DishKind.MEAT;
        Dishkind.DishKind other = Dishkind.DishKind.OTHER;
        Dishkind.DishKind fish = Dishkind.DishKind.FISH;
        Dishkind.DishKind vegetable = Dishkind.DishKind.VEGETABLE;
        List<Dish> dishes = Arrays.asList(
                new Dish("fish", 20.5f, fish, 888.888),
                new Dish("pork", 30.8f, meat, 66.88),
                new Dish("cabbage", 10.5f, vegetable, 888.888),
                new Dish("chicken", 60.5f, meat, 99.88),
                new Dish("beef", 80.5f, meat, 999.88)
        );
        // System.out.println(dishes.stream().collect(groupingBy(Dish::CaloriesType)));
        // achieve the two level divide
        System.out.println(dishes.stream().collect(groupingBy(Dish::getDishKind, groupingBy(
                Dish::CaloriesType, maxBy(Comparator.comparing(Dish::getCalories))))));


    }

    @Test
    public void shout07() {
        System.out.println(IntStream.of(1, 2, 3, 4, 5, 6).noneMatch(a -> a == 2));
    }

    @Test
    public void shout08() {
        // to the get highest calories' food in each type of food
        Dishkind.DishKind meat = Dishkind.DishKind.MEAT;
        Dishkind.DishKind other = Dishkind.DishKind.OTHER;
        Dishkind.DishKind fish = Dishkind.DishKind.FISH;
        Dishkind.DishKind vegetable = Dishkind.DishKind.VEGETABLE;
        List<Dish> dishes = Arrays.asList(
                new Dish("fish", 20.5f, fish, 888.888),
                new Dish("pork", 30.8f, meat, 66.88),
                new Dish("cabbage", 10.5f, vegetable, 888.888),
                new Dish("chicken", 60.5f, meat, 99.88),
                new Dish("beef", 80.5f, meat, 999.88)
        );
        System.out.println(dishes.stream().collect(groupingBy(Dish::getDishKind, collectingAndThen(maxBy(
                Comparator.comparingDouble(Dish::getCalories)), Optional::get)
        )));
    }

    @Test
    public void shout09() {
        // Using the mapping to grouping data from one type to another type
        Dishkind.DishKind meat = Dishkind.DishKind.MEAT;
        Dishkind.DishKind other = Dishkind.DishKind.OTHER;
        Dishkind.DishKind fish = Dishkind.DishKind.FISH;
        Dishkind.DishKind vegetable = Dishkind.DishKind.VEGETABLE;
        List<Dish> dishes = Arrays.asList(
                new Dish("fish", 20.5f, fish, 888.888),
                new Dish("pork", 30.8f, meat, 66.88),
                new Dish("cabbage", 10.5f, vegetable, 888.888),
                new Dish("chicken", 60.5f, meat, 99.88),
                new Dish("beef", 80.5f, meat, 999.88)
        );
        System.out.println(dishes.stream().collect(
                groupingBy(Dish::getDishKind, mapping(Dish::CaloriesType, toSet()))));

    }

    @Test
    public void shout10() {
        Stream<Dish> empty = Stream.empty();
        // System.out.println(empty.collect(groupingBy(Dish::getCalories, mapping(Dish::CaloriesType, toSet()))));
        System.out.println(empty.collect(groupingBy(Dish::getCalories, mapping(
                Dish::CaloriesType, toCollection(HashSet::new)))).get(null).getClass());
    }

    @Test
    public void shout11() {
        List<Dish> dishes = initDish();
        System.out.println(dishes.stream().collect(partitioningBy(Dish::ifHighCalories)));
        System.out.println(dishes.stream().filter(Dish::ifHighCalories).collect(toList()));
    }

    public List<Dish> initDish() {
        Dishkind.DishKind meat = Dishkind.DishKind.MEAT;
        Dishkind.DishKind other = Dishkind.DishKind.OTHER;
        Dishkind.DishKind fish = Dishkind.DishKind.FISH;
        Dishkind.DishKind vegetable = Dishkind.DishKind.VEGETABLE;
        return Arrays.asList(
                new Dish("fish", 20.5f, fish, 888.888),
                new Dish("pork", 30.8f, meat, 66.88),
                new Dish("cabbage", 10.5f, vegetable, 888.888),
                new Dish("chicken", 60.5f, meat, 99.88),
                new Dish("beef", 80.5f, meat, 999.88),
                new Dish("cauliflower", 15.6f, vegetable, 22.33),
                new Dish("pakchoi", 10.6f, vegetable, 6.8)
        );
    }

    @Test
    public void shout12() {
        // get the highest calories food of the each substreams
        System.out.println(initDish().stream().collect(partitioningBy(Dish::ifHighCalories, collectingAndThen(maxBy(
                Comparator.comparingDouble(Dish::getCalories)), Optional::get))));
    }

    @Test
    public void shout13() {
        // partition number into prime and nonprime
//        ArrayList<Integer> res = IntStream.iterate(0, a -> a + 1).limit(5).boxed().collect(toCollection(ArrayList::new));
//        System.out.println(res);
        Predicate<Integer> ifPrime = new Predicate<Integer>() {
            // To predicate a number if a prime number
            @Override
            public boolean test(Integer num) {
                int start = 2;
                int end = (int) Math.sqrt(num);
                for (int i = start; i <= end; i++) {
                    if (num % i == 0) {
                        return false;
                    }
                }
                return true;
            }
        };

        IntSupplier FIB = new IntSupplier() {
            private int curValues = 1;
            private int preValues = 0;

            @Override
            public int getAsInt() {
                int oldValues = preValues;
                preValues = curValues;
                curValues = oldValues + preValues;
                return curValues;
            }
        };
        System.out.println(IntStream.generate(FIB).limit(20).boxed().collect(partitioningBy(ifPrime)));
    }

    public boolean isPrime(int candidate) {
        return IntStream.rangeClosed(2, (int) Math.sqrt(candidate)).noneMatch(
                i -> candidate % i == 0);
    }

    @Test
    public void shout14() {
        // invoke the method isPrime
        System.out.println(isPrime(17));
    }

    @Test
    public void shout15() {
        System.out.println(IntStream.generate(new IntSupplier() {
            int old = 0;

            @Override
            public int getAsInt() {
                return old++;
            }
        }).limit(20).boxed().collect(partitioningBy(this::isPrime)));
    }

    @Test
    public void shout16() {
        System.out.println(IntStream.iterate(0, a -> ++a).limit(21).boxed().collect(partitioningBy(
                this::isPrime, toList()
        )));
    }

    @Test
    public void shout17() {
        /*Using this method, you can optimize the isPrime method by testing only the candidate prime
        against only the primes that are not greater than its square roo*/
        Map<Boolean, List<Integer>> res = supplier().get();
        System.out.println(res.size());

    }

    @Test
    public void shout18() {
        System.out.println(Runtime.getRuntime().availableProcessors());  // 4
        System.setProperty("Runtime.getRuntime().availableProcessors()", "12");
    }

    @Test
    public void shout19() {
        System.out.println(1_000_000);
    }

    @Test
    public void shout20() {
        //  long res = measureUtils.measurePerf(accumulator::sequentialAcc, 1_000_000);
        long res = measureUtils.measurePerf(accumulator::parallelAcc, 1_000_000);
        System.out.println("The fastest runtime is " + res + "msecs");
    }

}
